/**
 * created by 小卷毛, 2019-09-17 017
 * Copyright (c) 2019, 416143467@qq.com All Rights Reserved.
 * #                   *********                            #
 * #                  ************                          #
 * #                  *************                         #
 * #                 **  ***********                        #
 * #                ***  ****** *****                       #
 * #                *** *******   ****                      #
 * #               ***  ********** ****                     #
 * #              ****  *********** ****                    #
 * #            *****   ***********  *****                  #
 * #           ******   *** ********   *****                #
 * #           *****   ***   ********   ******              #
 * #          ******   ***  ***********   ******            #
 * #         ******   **** **************  ******           #
 * #        *******  ********************* *******          #
 * #        *******  ******************************         #
 * #       *******  ****** ***************** *******        #
 * #       *******  ****** ****** *********   ******        #
 * #       *******    **  ******   ******     ******        #
 * #       *******        ******    *****     *****         #
 * #        ******        *****     *****     ****          #
 * #         *****        ****      *****     ***           #
 * #          *****       ***        ***      *             #
 * #            **       ****        ****                   #
 */
package com.zhiyou.aifeng.common_ui.oss

import android.annotation.SuppressLint
import android.content.Context
import com.alibaba.sdk.android.oss.ClientConfiguration
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.common.OSSLog
import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.lzg.okrx.adapter.SingleBody
import com.lzy.okgo.OkGo
import com.lzy.okgo.convert.StringConvert
import com.lzy.okgo.utils.OkLogger
import com.zhiyou.aifeng.common.helper.TimeHelper
import com.zhiyou.aifeng.common_ui.utils.applyObservableSchedulers
import com.zhiyou.aifeng.common_ui.utils.applySchedulers
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxkotlin.toObservable
import io.reactivex.schedulers.Schedulers
import org.json.JSONObject
import java.util.concurrent.TimeUnit

class OSSHelper private constructor() {

    private var mClient: OSSClient? = null

    /**
     * OSS初始化，过期自动更新Token
     */
    @SuppressLint("CheckResult")
    fun initOSS(
        context: Context,
        endpoint: String = OSSConst.OSS_ENDPOINT,
        server: String = OSSConst.STS_SERVER_URL,
        connect: Int = 15 * 1000,
        socket: Int = 15 * 1000,
        max: Int = 5,
        error: Int = 2
    ) {
        OSSLog.enableLog()
        Completable.timer(100, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .subscribe {
                //需要在子线程中创建对象
                mClient = OSSClient(
                    context,
                    endpoint,
                    OSSAuthCredentialsProvider(server),
                    ClientConfiguration().apply {
                        connectionTimeout = connect //连接超时，默认15秒
                        socketTimeout = socket      //socket超时，默认15秒
                        maxConcurrentRequest = max  //最大并发请求书，默认5个
                        maxErrorRetry = error       //失败后最大重试次数，默认2次
                    }
                )
            }
    }

    /**
     * OSS初始化，手动更新Token
     */
    @SuppressLint("CheckResult")
    fun initOSSHand(
        context: Context,
        endpoint: String = OSSConst.OSS_ENDPOINT,
        server: String = OSSConst.STS_SERVER_URL,
        connect: Int = 15 * 1000,
        socket: Int = 15 * 1000,
        max: Int = 5,
        error: Int = 2,
        onFinish: (Boolean) -> Unit = {}
    ) {
        OkGo.post<String>(server)
            .converter(StringConvert())
            .adapt(SingleBody<String>())
            .observeOn(Schedulers.io())
            .map {
                val jsonObj = JSONObject(it)
                if (jsonObj.has("AccessKeyId")
                    && jsonObj.has("AccessKeySecret")
                    && jsonObj.has("SecurityToken")
                ) {
                    OSSLog.enableLog()
                    val credentialProvider = OSSStsTokenCredentialProvider(
                        jsonObj.optString("AccessKeyId"),
                        jsonObj.optString("AccessKeySecret"),
                        jsonObj.optString("SecurityToken")
                    )

                    mClient = OSSClient(
                        context,
                        endpoint,
                        credentialProvider,
                        ClientConfiguration().apply {
                            connectionTimeout = connect //连接超时，默认15秒
                            socketTimeout = socket      //socket超时，默认15秒
                            maxConcurrentRequest = max  //最大并发请求书，默认5个
                            maxErrorRetry = error       //失败后最大重试次数，默认2次
                        }
                    )
                }
                return@map mClient != null
            }
            .compose(applySchedulers())
            .subscribeBy(onSuccess = onFinish)
    }

    /**
     * OSS上传单个文件
     */
    @SuppressLint("CheckResult")
    fun uploadImage(
        path: String = "",
        onBefore: () -> Unit = {},
        onAfter: () -> Unit = {},
        onProgress: (PutObjectRequest, Long, Long) -> Unit = { _, _, _ -> },
        onSuccess: (PutObjectResult) -> Unit = {},
        OnError: (Exception) -> Unit = {}
    ) {

        requireNotNull(mClient) { "OSSClient is not initialized, please call the init method first!" }
        require(path.isNotEmpty()) { "path cannot be empty!" }

        Single.just(path)
            .map {
                //上传的图片根据时间进行分组
                val timeFolder = TimeHelper.getInstance().getNowTime("yyyy-MM/dd/")
                val fileName = "picture_${System.currentTimeMillis()}.jpg"
                val mRequest = PutObjectRequest(
                    OSSConst.BUCKET_NAME,
                    "${OSSConst.OBJECT_KEY}/${timeFolder}${fileName}",
                    it
                )

                mRequest.setProgressCallback { request, currentSize, totalSize ->
                    onProgress.invoke(request, currentSize, totalSize)
                }

                try {
                    return@map mClient!!.putObject(mRequest)
                        .apply {
                            responseHeader["url"] = OSSConst.SERVER_URL + timeFolder + fileName
                        }
                } catch (exception: ClientException) {
                    //本地异常，如网络异常等
                    OkLogger.e(exception.message)

                    OnError.invoke(exception)
                } catch (exception: ServiceException) {
                    //服务异常
                    OkLogger.e("ErrorCode:${exception.errorCode}")
                    OkLogger.e("RequestId:${exception.requestId}")
                    OkLogger.e("HostId:${exception.hostId}")
                    OkLogger.e("RawMessage:${exception.rawMessage}")

                    OnError.invoke(exception)
                }
                return@map null
            }
            .compose(applySchedulers())
            .doOnSubscribe { onBefore.invoke() }
            .doFinally { onAfter.invoke() }
            .subscribe(
                {
                    if (it != null) onSuccess.invoke(it)
                    else OkLogger.e("Oss failed to upload pictures!")
                },
                {
                    OkLogger.e(it.message)
                    OnError.invoke(it as Exception)
                }
            )
    }

    /**
     * OSS上传多个文件
     */
    @SuppressLint("CheckResult")
    fun uploadImages(
        pathList: List<String> = ArrayList(),
        onBefore: () -> Unit = {},
        onAfter: () -> Unit = {},
        onProgress: (Int, PutObjectRequest, Long, Long) -> Unit = { _, _, _, _ -> },
        onSuccess: (Int, PutObjectResult?) -> Unit = { _, _ -> },
        OnError: (Int, Exception) -> Unit = { _, _ -> }
    ) {
        requireNotNull(mClient) { "OSSClient is not initialized, please call the init method first!" }
        require(pathList.isNotEmpty()) { "pathList cannot be empty!" }

        pathList.toObservable()
            .concatMap { Observable.just(it) }
            .map {
                //上传的图片根据时间进行分组
                val timeFolder = TimeHelper.getInstance().getNowTime("yyyy-MM/dd/")
                val fileName = "picture_${System.currentTimeMillis()}.jpg"
                val mRequest = PutObjectRequest(
                    OSSConst.BUCKET_NAME,
                    "${OSSConst.OBJECT_KEY}/${timeFolder}${fileName}",
                    it
                )

                mRequest.setProgressCallback { request, currentSize, totalSize ->
                    onProgress(pathList.indexOf(it), request, currentSize, totalSize)
                }

                try {
                    return@map mClient!!.putObject(mRequest)
                        .apply {
                            eTag = pathList.indexOf(it).toString()
                            responseHeader["url"] = OSSConst.SERVER_URL + timeFolder + fileName
                        }
                } catch (exception: ClientException) {
                    //本地异常，如网络异常等
                    OkLogger.e(exception.message)

                    OnError(pathList.indexOf(it), exception)
                } catch (exception: ServiceException) {
                    //服务异常
                    OkLogger.e("ErrorCode:${exception.errorCode}")
                    OkLogger.e("RequestId:${exception.requestId}")
                    OkLogger.e("HostId:${exception.hostId}")
                    OkLogger.e("RawMessage:${exception.rawMessage}")

                    OnError(pathList.indexOf(it), exception)
                }
                return@map null
            }
            .compose(applyObservableSchedulers())
            .doOnSubscribe { onBefore() }
            .doFinally { onAfter() }
            .subscribe(
                {
                    it?.let { onSuccess((it.eTag).toInt(), it) }
                },
                {
                    OkLogger.e(it.message)
                    OnError(-1, it as Exception)
                },
                { onSuccess(pathList.size, null) }
            )
    }

    companion object {
        private var instance: OSSHelper? = null

        @JvmStatic
        val manager: OSSHelper
            get() {
                if (instance == null) instance = OSSHelper()
                return instance!!
            }
    }

}